package com.ydlclass.spi;

import com.ydlclass.annotation.SPI;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;


@Slf4j
public final class SpiLoader<T> {

    private static final String SERVICE_DIRECTORY = "META-INF/service/";
    // 缓存
    private static final Map<Class<?>, SpiLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    // 成员变量，每一个spiLoader维护一个class和一直匹配的实例
    private final Class<?> type;
    private final Map<String, SpiHolder<Object>> cachedInstances = new ConcurrentHashMap<>();
    private final SpiHolder<Map<String, Class<?>>> cachedClasses = new SpiHolder<>();


    /**
     * 指定一个类型的spiLoader
     * @param type 传入一个类型
     */
    private SpiLoader(Class<?> type) {
        this.type = type;
    }

    /**
     * 通过类型获取一个
     * @param type
     * @return
     * @param <S>
     */
    public static <S> SpiLoader<S> getSpiLoader(Class<S> type) {
        if (type == null) {
            throw new IllegalArgumentException("The spi type should not be null.");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("The spi type must be an interface.");
        }
        if (type.getAnnotation(SPI.class) == null) {
            throw new IllegalArgumentException("The spi type must be annotated by @SPI");
        }
        // 尝试存缓存中获取一个loader，如果没有命中，则创建一个
        SpiLoader<S> extensionLoader = (SpiLoader<S>) EXTENSION_LOADERS.get(type);
        if (extensionLoader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new SpiLoader<S>(type));
            extensionLoader = (SpiLoader<S>) EXTENSION_LOADERS.get(type);
        }
        return extensionLoader;
    }

    /**
     * 通过名字按需加载
     * @param name spi的实例的名字
     * @return 目标实例
     */
    public T getExtension(String name) {
        if (name == null || "".equals(name)) {
            throw new IllegalArgumentException("Extension name should not be null or empty.");
        }
        // 首先从缓存中获取，如果没有命中，则创建一个Holder
        SpiHolder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new SpiHolder<>());
            holder = cachedInstances.get(name);
        }
        // 如果实例不存在，我们直接创建一个单例，放入holder中
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    /**
     *
     * @param tClass
     * @param name
     * @return
     * @param <S>
     */
    public static <S> S get(Class<S> tClass,String name){
        SpiLoader<S> spiLoader = getSpiLoader(tClass);
        return spiLoader.getExtension(name);
    }

    /**
     * 根据名字创建扩展实例
     * @param name
     * @return
     */
    private T createExtension(String name) {
        // load all extension classes of type T from file and get specific one by name
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("No such extension of name " + name);
        }
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            try {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        return instance;
    }

    /**
     * 加载spi的所有实现类
     * @return Map<String, Class<?>>
     */
    private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取已经加载的spi扩展类
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = new HashMap<>();
                    // 加载所有的spi->class
                    loadDirectory(classes);
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    /**
     * 加载spi文件中的class
     * @param extensionClasses
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses) {
        String fileName = SpiLoader.SERVICE_DIRECTORY + type.getName();
        try {
            Enumeration<URL> urls;
            ClassLoader classLoader = SpiLoader.class.getClassLoader();
            urls = classLoader.getResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL resourceUrl = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceUrl);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 加载资源
     * @param extensionClasses
     * @param classLoader
     * @param resourceUrl
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
            String line;
            // 以行的形式读取，spi文件中的一项内容不能随便换行
            while ((line = reader.readLine()) != null) {
                // 处理注释   xxxx=yyyyyy #mmmmm  | #nnnnnnnnn
                final int ci = line.indexOf('#');
                if(ci == 0){
                    continue;
                }
                if (ci > 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        final int delimiterPosition = line.indexOf('=');
                        String name = line.substring(0, delimiterPosition).trim();
                        String clazzName = line.substring(delimiterPosition + 1).trim();
                        if (name.length() > 0 && clazzName.length() > 0) {
                            Class<?> clazz = classLoader.loadClass(clazzName);
                            extensionClasses.put(name, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        log.error(e.getMessage());
                    }
                }

            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    public Map<String, Class<?>> get(){
        return cachedClasses.get();
    }
}
